/*
 * ArbolContextosPPMC.cpp
 *
 *  Created on: 29/04/2012
 *      Author: lucas
 */

#include "../hdr/ArbolContextosPPMC.h"
#include "../hdr/Vector.h"
#include <stdio.h>
#include <string.h>

#define LIMITE_DE_NODOS 2500000

ArbolContextosPPMC::ArbolContextosPPMC(FILE *archivo, int comprime) {
	lector = NULL;
	emisor = NULL;
	if (comprime)
		emisor = new EmisorAritmetico(archivo);
	else
		lector = new LectorAritmetico(archivo);
}

void ArbolContextosPPMC::terminarEmision() {
	if (emisor)
		emisor->finalizarEmision();
}

ArbolContextosPPMC::~ArbolContextosPPMC() {
	if (emisor)
		delete emisor;
	else
		delete lector;
}

int ArbolContextosPPMC::procesarCaracter(unsigned short caracter,
		unsigned char* contexto, size_t orden) {
	if (NodoContexto::getCantidadNodos() > LIMITE_DE_NODOS)
		nodoOrden0.limpiar();
	char excluir[256];
	memset(excluir, 0, 256);
	if (_procesarCaracter(&nodoOrden0, caracter, contexto, orden, 0, excluir)
			== 1)
		return 1;
	int exclusiones = nodoOrden0.getContexto()->getCantidadDeCaracteres() - 1;
	if (caracter >= 256)
		exclusiones++;
	int exclusionesMenores =
			nodoOrden0.getContexto()->getCantidadDeCaracteresMenores(caracter);
	emisor->emitirSimbolo(caracter - exclusionesMenores, 1, 257 - exclusiones);
//	printf(
//			"Contexto de orden -1: Emite '%c' con probabilidad 1/%d (piso %d)\n\n",
//			caracter, 257 - exclusiones, caracter - exclusionesMenores); // Debug
	return 0;
}

/* En proxExclusion, guarda la direccion de memoria del vector de caracteres del 
 * contexto actual, para pasarselo al contexto de orden inferior.
 * En aExcluir obtiene la direccion del vector de caracteres del contexto de orden
 * superior, para saber que caracteres excluir en este contexto.
 */
int ArbolContextosPPMC::_procesarCaracter(NodoContexto* nodo,
		unsigned short caracter, unsigned char* contexto, size_t orden,
		size_t profundidad, char* proxExclusion) {
	NodoContexto* proximoNodo = NULL;
	if (profundidad < orden) {
		proximoNodo = nodo->getNodoContextoSuperior(
				contexto[orden - profundidad - 1]);
		if (_procesarCaracter(proximoNodo, caracter, contexto, orden,
				profundidad + 1, proxExclusion) == 1)
			return 1;
	}
//	printf("Contexto orden %d\n", profundidad);
	return nodo->getContexto()->emitirProbabilidad(caracter, proxExclusion,
			this->emisor);
}

/* 'contexto' es el contexto donde se está procesando. 'orden' indica el orden de la 
 * compresion. 'caracter' es una direccion de memoria valida donde se devuelve
 * el caracter procesado. 
 * Devuelve 1 en caso de haber procesado el EOF, 0 en cualquier otro caso. */
int ArbolContextosPPMC::procesarProbabilidad(unsigned char* caracter,
		unsigned char* contexto, size_t orden) {
	if (NodoContexto::getCantidadNodos() > LIMITE_DE_NODOS)
		nodoOrden0.limpiar();
	Vector<short> *excluidos = NULL;
	if (_procesarProbabilidad(&nodoOrden0, caracter, contexto, orden, 0,
			&excluidos) == 1)
		return 0;
	if (procesarProbabilidadMenosUno(caracter, excluidos) == 1)
		return 1;
	actualizaCaracterLeido(&nodoOrden0, caracter, contexto, orden, 0);
	return 0;
}

int ArbolContextosPPMC::_procesarProbabilidad(NodoContexto* nodo,
		unsigned char* caracter, unsigned char* contexto, size_t orden,
		size_t profundidad, Vector<short> **proxExclusion) {
	NodoContexto* proximoNodo = NULL;
	Vector<short> *aExcluir = NULL;
	if (profundidad < orden) {
		proximoNodo = nodo->getNodoContextoSuperior(
				contexto[orden - profundidad - 1]);
		if (_procesarProbabilidad(proximoNodo, caracter, contexto, orden,
				profundidad + 1, &aExcluir) == 1)
			return 1;
	}
	int res = nodo->getContexto()->emitirCaracter(caracter, aExcluir,
			proxExclusion, this->lector);
	// res = 1 => encontro caracter; res = 0 => emitio ESC
	if (res)
		this->actualizaCaracterLeido(nodo, caracter, contexto, orden,
				profundidad);
	return res;
}

/* En caso de procesar el EOF devuelve 1. 0 en cualquier otro caso. */
int ArbolContextosPPMC::procesarProbabilidadMenosUno(unsigned char* caracter,
		Vector<short>* excluidos) {
	Vector<short> auxCaracteres;
	unsigned short largos[257];
	unsigned short cantidadTotal = 0;
	for (unsigned int simbolo = 0; simbolo < 257; simbolo++) {
		if (excluidos && excluidos->contiene((short) simbolo)) {
//			printf("En Modelo (-1) excluyo el caracter (%c)\n", (char) simbolo);
			continue;
		}
		largos[cantidadTotal] = 1;
		cantidadTotal++;
		auxCaracteres.append(simbolo);
	}
	int res = lector->leerSimbolo(largos, cantidadTotal, cantidadTotal);
	*caracter = auxCaracteres[res];
//	printf(
//			"luego de leerSimbolo en Modelo (-1), el indice es (%d) y el caracter es (%c)\n",
//			res, *caracter);
	if (res == cantidadTotal - 1) // Este es el EOF
		return 1;
	return 0;
}

// Durante la descompresion, Una vez que obtuve el caracter debo agregarlo en los contextos 
// superiores
void ArbolContextosPPMC::actualizaCaracterLeido(NodoContexto* nodo,
		unsigned char* caracter, unsigned char* contexto, size_t orden,
		size_t profundidad) {
	NodoContexto* proximoNodo = NULL;
	if (profundidad < orden) {
		proximoNodo = nodo->getNodoContextoSuperior(
				contexto[orden - profundidad - 1]);
		this->actualizaCaracterLeido(proximoNodo, caracter, contexto, orden,
				profundidad + 1);
	}
	nodo->getContexto()->actualizarContexto(*caracter);
}
